package com.google.code.chaotic.server;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.code.chaotic.ConversionUtil;
import com.google.code.chaotic.dto.CommutingHabitDto;
import com.google.code.chaotic.dto.CommutingServiceDto;
import com.google.code.chaotic.dto.HabitInServiceDto;
import com.google.code.chaotic.dto.ServiceWaypointDto;
import com.google.code.chaotic.dto.UserType;
import com.google.code.chaotic.model.CommutingHabit;
import com.google.code.chaotic.model.CommutingService;
import com.google.code.chaotic.model.HabitInService;
import com.google.code.chaotic.model.ServiceWaypoint;
import com.google.code.chaotic.rpc.ServiceSvc;
import com.google.code.chaotic.shared.AuthException;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

//

public class ServiceSvcImpl extends RemoteServiceServlet //
		implements ServiceSvc {

	// @PersistenceUnit(unitName = "backTierWebLocal")
	// private EntityManagerFactory emf;

	// @PersistenceContext(unitName = "backTierWebJTA", type = TRANSACTION)
	// private EntityManager em;

	@Override
	public int countAllServices(String sessionId) throws AuthException {
		ServerSessionManager.assertRole(sessionId, UserType.ADMIN);
		return countMatchingServices(null);
	}

	@Override
	public int countOwnServices(String sessionId) throws AuthException {
		ServerSessionManager.assertRole(sessionId, UserType.COMMUTER);
		return countMatchingServices(getUserId(sessionId));
	}

	public int countMatchingServices(String commuterId) {
		// EntityManager em = EmFactory
		// .getEntityManager(getThreadLocalRequest());
		PersistenceManager pm = PmFactory.get().getPersistenceManager();
		try {
			// String jpql = (commuterId != null ?
			//
			// "SELECT COUNT(DISTINCT s) FROM CommutingService s"
			// + " JOIN s.habits his "
			// + " WHERE his.habit.commuter.id = ?1" //
			//
			// : "SELECT COUNT(s) FROM CommutingService s");
			//
			// Query query = em.createQuery(jpql);
			// if (commuterId != null)
			// query.setParameter(1, commuterId);
			// Number serviceCount = (Number) query.getSingleResult();

			Number serviceCount = 0;
			if (commuterId != null) {
				// services = (List<CommutingService>) query1.execute();

				Query query2 = pm.newQuery(CommutingHabit.class,
						"parentId == :id");
				query2.setResult("id");
				List<String> habitIds = (List<String>) //
				query2.execute(commuterId);

				if (habitIds.size() > 0) {
					Query query3 = pm.newQuery(HabitInService.class,
							":p.contains(habitId)");
					query3.setResult("COUNT(DISTINCT this)");
					serviceCount = (Number) query3.execute(habitIds);
				}

			} else {
				Query query = pm.newQuery(CommutingService.class);
				query.setResult("COUNT(DISTINCT this)");
				serviceCount = (Number) query.execute();
			}

			System.out.println("Service count = " + serviceCount);
			return serviceCount.intValue();

		} catch (RuntimeException e) {
			e.printStackTrace();
			throw e;

		} finally {
			// if (em != null && em.isOpen())
			// em.close();
			if (pm != null && !pm.isClosed())
				pm.close();
		}
	}

	@Override
	public List<CommutingServiceDto> listAllServicesPaginated( //
			String sessionId, int pageNo, int fetchSize) //
			throws AuthException {

		ServerSessionManager.assertRole(sessionId, UserType.ADMIN);
		// EntityManager em = EmFactory
		// .getEntityManager(getThreadLocalRequest());
		PersistenceManager pm = PmFactory.get().getPersistenceManager();
		try {
			// return listMatchingServicesPaginated(em, pageNo, fetchSize,
			// null);
			return listMatchingServicesPaginated(pm, pageNo, fetchSize, null);

		} finally {
			// if (em.isOpen())
			// em.close();
			if (!pm.isClosed())
				pm.close();
		}
	}

	@Override
	public List<CommutingServiceDto> listOwnServicesPaginated( //
			String sessionId, int pageNo, int fetchSize) //
			throws AuthException {

		ServerSessionManager.assertRole(sessionId, UserType.COMMUTER);
		// EntityManager em = EmFactory
		// .getEntityManager(getThreadLocalRequest());
		PersistenceManager pm = PmFactory.get().getPersistenceManager();
		try {
			// return listMatchingServicesPaginated(em, pageNo, fetchSize,
			// getUserId(sessionId));
			return listMatchingServicesPaginated(pm, pageNo, fetchSize,
					getUserId(sessionId));

		} finally {
			// if (em.isOpen())
			// em.close();
			if (pm.isClosed())
				pm.close();
		}
	}

	private List<CommutingServiceDto> listMatchingServicesPaginated( //
			/* EntityManager em */PersistenceManager pm, //
			int pageNo, int fetchSize, String commuterId) {

		int fromIndex = (pageNo - 1) * fetchSize;
		int toIndex = fromIndex + fetchSize;

		// String jpql = (commuterId != null ?
		//
		// "SELECT DISTINCT s FROM CommutingService s" + " JOIN s.habits his "
		// + " WHERE his.habit.commuter.id = ?1" //
		// + " ORDER BY s.id" //
		//
		// : "SELECT s FROM CommutingService s ORDER BY s.id");
		//
		// Query query = em.createQuery(jpql);
		// if (commuterId != null)
		// query.setParameter(1, commuterId);

		// // TODO: Must be parameterized!
		// fetchSize = Math.min(50, fetchSize);
		// query.setFirstResult((fromIndex);
		// query.setMaxResults(fetchSize);

		// List<CommutingService> services = query.getResultList();

		List<CommutingService> services = null;
		if (commuterId != null) {
			// services = (List<CommutingService>) query1.execute();
			services = new LinkedList<CommutingService>();

			Query query2 = pm.newQuery(CommutingHabit.class, //
					"parentId == :id");
			query2.setResult("id");
			List<String> habitIds = (List<String>) query2.execute(commuterId);
			System.out.println("commuterId = " + commuterId);
			if (habitIds.size() > 0) {
				for (String tempHabitId : habitIds)
					System.out.println("Matched Habit ID: " + tempHabitId);

				Query query3 = pm.newQuery(HabitInService.class,
						":p.contains(habitId)");
				List<HabitInService> hisList = (List<HabitInService>) //
				query3.execute(habitIds);
				for (HabitInService his : hisList)
					services.add(his.getService());
				services = services.subList(//
						fromIndex, //
						Math.min(toIndex, services.size()));
			}
		} else {
			Query query1 = pm.newQuery(CommutingService.class);
			query1.setOrdering("id");
			query1.setRange(fromIndex, toIndex);
			services = (List<CommutingService>) query1.execute();
		}

		List<CommutingServiceDto> serviceDtos = //
		new ArrayList<CommutingServiceDto>(services.size());

		List<String> habitIds = new LinkedList<String>();

		for (CommutingService service : services) {
			CommutingServiceDto serviceDto = ConversionUtil
					.entityToDto(service);
			serviceDtos.add(serviceDto);

			for (HabitInServiceDto hisDto : serviceDto.getHabits()) {
				habitIds.add(hisDto.getHabitId());
			}
		}
		if (!habitIds.isEmpty()) {
			Query query4 = pm.newQuery(//
					CommutingHabit.class, ":p.contains(id)");
			List<CommutingHabit> habits = (List<CommutingHabit>) //
			query4.execute(habitIds);
			HashMap<String, CommutingHabit> habitsMap = //
			new HashMap<String, CommutingHabit>(habitIds.size());
			for (CommutingHabit habit : habits)
				habitsMap.put(habit.getId(), habit);
			for (CommutingServiceDto serviceDto : serviceDtos)
				for (HabitInServiceDto hisDto : serviceDto.getHabits()) {
					CommutingHabit habit = habitsMap.get(hisDto.getHabitId());
					CommutingHabitDto habitDto = ConversionUtil
							.entityToDto(habit);
					hisDto.setHabit(habitDto);
				}
		}
		return serviceDtos;
	}

	private String getUserId(String sessionId) {
		return ServerSessionManager.getInstance().getUser(sessionId).getId();
	}

	@Override
	public void finalizeService(String sessionId, String serviceId,
			ArrayList<ServiceWaypointDto> waypointDtos) //
			throws AuthException {

		ServerSessionManager.assertRole(sessionId, UserType.ADMIN);

		// EntityManager em = EmFactory
		// .getEntityManager(getThreadLocalRequest());
		PersistenceManager pm = PmFactory.get().getPersistenceManager();

		ArrayList<ServiceWaypoint> waypointEntities = //
		new ArrayList<ServiceWaypoint>(waypointDtos.size());

		// CommutingService service = em.find(CommutingService.class,
		// serviceId);
		CommutingService service = pm.getObjectById(CommutingService.class,
				serviceId);
		for (ServiceWaypointDto dto : waypointDtos)
			waypointEntities.add(ConversionUtil.dtoToEntity(dto, service));

		try {
			// EntityTransaction tx = em.getTransaction();
			Transaction tx = pm.currentTransaction();
			tx.begin();
			service.setFinalized(true);
			service.setWaypoints(waypointEntities);
			// em.persist(service);
			pm.makePersistent(service);
			tx.commit();

		} finally {
			// em.close();
			pm.close();
		}
	}
}
